package com.heima.wemedia.service.impl;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.api.R;
import com.heima.article.feign.ApArticleFeign;
import com.heima.common.aliyun.GreenImageScan;
import com.heima.common.aliyun.GreenTextScan;
import com.heima.common.contants.MQConstants;
import com.heima.common.dtos.ResponseResult;
import com.heima.common.exception.LeadNewsException;
import com.heima.common.minio.MinIOFileStorageService;
import com.heima.common.tess4j.Tess4jClient;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.wemedia.pojos.WmChannel;
import com.heima.model.wemedia.pojos.WmNews;
import com.heima.model.wemedia.pojos.WmSensitive;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.common.BeanHelper;
import com.heima.utils.common.JsonUtils;
import com.heima.utils.common.SensitiveWordUtil;
import com.heima.wemedia.mapper.WmChannelMapper;
import com.heima.wemedia.mapper.WmNewsMapper;
import com.heima.wemedia.mapper.WmSensitiveMapper;
import com.heima.wemedia.mapper.WmUserMapper;
import com.heima.wemedia.service.WmNewsAutoScanService;
import com.heima.wemedia.service.WmNewsService;
import com.heima.wemedia.service.WmNewsTaskService;
import io.seata.spring.annotation.GlobalTransactional;
import net.sourceforge.tess4j.ITesseract;
import net.sourceforge.tess4j.TesseractException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class WmNewsAutoScanServiceImpl implements WmNewsAutoScanService {
    @Autowired
    private WmNewsMapper wmNewsMapper;
    @Autowired
    private MinIOFileStorageService storageService;
    @Autowired
    private GreenTextScan greenTextScan;
    @Autowired
    private GreenImageScan greenImageScan;
    @Autowired
    private ApArticleFeign apArticleFeign;
    @Autowired
    private WmUserMapper wmUserMapper;
    @Autowired
    private WmChannelMapper wmChannelMapper;
    @Autowired
    private WmSensitiveMapper wmSensitiveMapper;
    @Autowired
    private ITesseract tesseract;
    @Autowired
    private WmNewsTaskService wmNewsTaskService;
    @Autowired
    private KafkaTemplate<String,String> kafkaTemplate;
    @Override
    @Async  //把该方法声明为异步线程方法，原理：调用该方法独立创建新线程来执行
    @GlobalTransactional
    public void autoScanWmNews(Integer id) {
        //查询自媒体文章
        WmNews wmNews = wmNewsMapper.selectById(id);

        if(!wmNews.getStatus().equals(WmNews.Status.SUBMIT.getCode())){
            //如果文章状态不是提交审核，则无需审核
            return;
        }

        //提取文章的文本和图片
        List<byte[]> imageList = getImageFromWmNews(wmNews);
        List<String> textList = getTextFromWmNews(wmNews,imageList);

        //检测自定义敏感词
        try {
            boolean flag = handleSensitiveScan(textList,wmNews);
            if(!flag)return;
        } catch (Exception e) {
            e.printStackTrace();
            throw new LeadNewsException(500,e.getMessage());
        }

        //调用阿里云内容检测审核文本，根据结果修改文章的状态
        try {
            Map result = greenTextScan.greeTextScan(textList);
            boolean flag = handleScanResult(result,wmNews);
            if(!flag)return;//失败则退出
        } catch (Exception e) {
            e.printStackTrace();
            throw new LeadNewsException(500,"阿里云接口调用失败："+e.getMessage());
        }

        //调用阿里云内容检测审核图片，根据结果修改文章的状态
        try {
            Map result = greenImageScan.imageScan(imageList);
            boolean flag = handleScanResult(result,wmNews);
            if(!flag)return;//失败则退出
        } catch (Exception e) {
            e.printStackTrace();
            throw new LeadNewsException(500,"阿里云接口调用失败："+e.getMessage());
        }

        //判断发布时间如果大于当前时间，文章状态为8，暂时不发布
        if(wmNews.getPublishTime()!=null && wmNews.getPublishTime().after(new Date())){
            wmNews.setStatus(WmNews.Status.SUCCESS.getCode());
            wmNews.setReason("文章审核通过，待发表");
            wmNewsMapper.updateById(wmNews);

            //添加文章定时发布任务到延迟队列
            wmNewsTaskService.addTaskToList(wmNews);

            return; // 不能往下执行
        }

        //发表文章：把自媒体文章存入App文章表（调用Feign接口）
        saveApArticle(wmNews);
    }

    /**
     * 自定义敏感词检测
     * @param wmNews
     */
    private boolean handleSensitiveScan(List<String> textList,WmNews wmNews) {
        boolean flag = true;
        //查询自定义敏感词列表
        //可以优化：第一次查询数据库，第n次查询redis
        List<WmSensitive> wmSensitives = wmSensitiveMapper.selectList(null);

        //使用DFA算法构建词库
        List<String> wordList = wmSensitives.stream().map(WmSensitive::getSensitives).collect(Collectors.toList());
        SensitiveWordUtil.initMap(wordList);
        
        //使用DFA词库匹配文章内容
        //准备匹配的内容
        String content = textList.stream().collect(Collectors.joining(""));
        Map<String, Integer> result = SensitiveWordUtil.matchWords(content);

        //根据结果进行处理文章状态
        if(result!=null && result.size()>0){
            flag = false;
            //修改文章状态
            wmNews.setStatus(WmNews.Status.FAIL.getCode());
            wmNews.setReason("文章包含违规词："+result.keySet());
            wmNewsMapper.updateById(wmNews);
        }
        return flag;
    }

    /**
     * 把自媒体文章导入到App库
     * @param wmNews
     */
    public void saveApArticle(WmNews wmNews) {
        ArticleDto dto = BeanHelper.copyProperties(wmNews,ArticleDto.class);
        dto.setLayout(wmNews.getType());
        dto.setFlag((byte)0);
        dto.setLikes(0);
        dto.setComment(0);
        dto.setCollection(0);
        dto.setViews(0);

        //设置作者信息
        WmUser wmUser = wmUserMapper.selectById(wmNews.getUserId());
        if(wmNews!=null){
            dto.setAuthorId(Long.valueOf(wmUser.getId()));
            dto.setAuthorName(wmUser.getName());
        }

        //设置频道信息
        WmChannel channel = wmChannelMapper.selectById(wmNews.getChannelId());
        if(channel!=null){
            dto.setChannelId(channel.getId());
            dto.setChannelName(channel.getName());
        }

        //保存App库文章信息
        ResponseResult<Long> responseResult = apArticleFeign.saveApArticle(dto);

        //更新自媒体表信息
        if(responseResult.getCode().equals(200)){
            //文章状态为9，且设置App文章ID
            wmNews.setStatus(WmNews.Status.PUBLISHED.getCode());
            wmNews.setReason("文章已发表");
            wmNews.setArticleId(responseResult.getData());
            wmNewsMapper.updateById(wmNews);
            //使用MQ同步文章数据到ES中
            kafkaTemplate.send(MQConstants.WM_NEW_UP_ES_TOPIC,responseResult.getData().toString());
        }
    }

    /**
     * 处理阿里云内容检测结果
     * @param result
     * @param wmNews
     * @return
     */
    private boolean handleScanResult(Map result, WmNews wmNews) {
        boolean flag = false;
        //取出检测结果
        String suggestion = (String)result.get("suggestion");
        if(StringUtils.isNotEmpty(suggestion)){
            //根据结果处理文章状态
            if("pass".equals(suggestion)){
                flag = true;
            }
            if("block".equals(suggestion)){
                //修改文章状态为2
                wmNews.setStatus(WmNews.Status.FAIL.getCode());
                wmNews.setReason("文章包含违规内容");
                wmNewsMapper.updateById(wmNews);
            }
            if("review".equals(suggestion)){
                //修改文章状态为3
                wmNews.setStatus(WmNews.Status.ADMIN_AUTH.getCode());
                wmNews.setReason("文章包含可疑内容，待人工进一步审核");
                wmNewsMapper.updateById(wmNews);
            }
        }
        return flag;
    }

    private List<byte[]> getImageFromWmNews(WmNews wmNews) {
        List<byte[]> imageList = new ArrayList<>();
        //内容图片
        if(StringUtils.isNotEmpty(wmNews.getContent())){
            List<Map> list = JsonUtils.toList(wmNews.getContent(),Map.class);
            for(Map map:list){
                if(map.get("type").equals("image")){
                    String url = (String)map.get("value");

                    //通过url从MinIO下载该图片内容
                    byte[] image = storageService.downLoadFile(url);
                    imageList.add(image);
                }
            }
        }

        //封面图片
        if(StringUtils.isNotEmpty(wmNews.getImages())){
            String[] split = wmNews.getImages().split(",");
            for(String url:split){
                //通过url从MinIO下载该图片内容
                byte[] image = storageService.downLoadFile(url);
                imageList.add(image);
            }
        }
        return imageList;
    }

    /**
     * 提取文章中文字内容
     * @param wmNews
     * @return
     */
    private List<String> getTextFromWmNews(WmNews wmNews,List<byte[]> imageList) {
        List<String> textList = new ArrayList<>();
        //内容
        if(StringUtils.isNotEmpty(wmNews.getContent())){
            List<Map> list = JsonUtils.toList(wmNews.getContent(),Map.class);
            for(Map map:list){
                if(map.get("type").equals("text")){
                    textList.add((String)map.get("value"));
                }
            }
        }
        //标题
        if(StringUtils.isNotEmpty(wmNews.getTitle())){
            textList.add(wmNews.getTitle());
        }
        //标签
        if(StringUtils.isNotEmpty(wmNews.getLabels())){
            textList.add(wmNews.getLabels());
        }

        //从图片中提取文字
        if(CollectionUtils.isNotEmpty(imageList)){

            for(byte[] image:imageList){
                //把字节数组临时存入ByteArrayInputStream字节输入流
                ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(image);
                try {
                    BufferedImage bufferedImage = ImageIO.read(byteArrayInputStream);
                    String result = tesseract.doOCR(bufferedImage);
                    if(StringUtils.isNotEmpty(result)){
                        textList.add(result);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new RuntimeException("OCR识别异常："+e.getMessage());
                }
            }
        }

        return textList;
    }
}
